/**
 * 
 */
package edu.ubc.javascript;

import static edu.ubc.javascript.ASTFactory.name;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import com.google.javascript.jscomp.mozilla.rhino.Node;
import com.google.javascript.jscomp.mozilla.rhino.Token;
import com.google.javascript.jscomp.mozilla.rhino.ast.ArrayLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.Assignment;
import com.google.javascript.jscomp.mozilla.rhino.ast.AstNode;
import com.google.javascript.jscomp.mozilla.rhino.ast.Block;
import com.google.javascript.jscomp.mozilla.rhino.ast.ConditionalExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.EmptyExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.FunctionCall;
import com.google.javascript.jscomp.mozilla.rhino.ast.FunctionNode;
import com.google.javascript.jscomp.mozilla.rhino.ast.IfStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.InfixExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.Name;
import com.google.javascript.jscomp.mozilla.rhino.ast.NodeVisitor;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectProperty;
import com.google.javascript.jscomp.mozilla.rhino.ast.ParenthesizedExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.PropertyGet;
import com.google.javascript.jscomp.mozilla.rhino.ast.UnaryExpression;

public class GetterSetter implements NodeVisitor {

	private ReflectiveAstTransformer tx;
	
	public GetterSetter(ReflectiveAstTransformer tx) {
		this.tx = tx;
	}
	
	private int count = 0;
	
	private String newName() {
		return "_tmp" + (count++);
	}
	
	private Node transform(boolean lvalue, AstNode expr, List<Node> acc) {
		if(expr == null) {
			return expr;
		}
		if(ContextCheck.isAssignmentOp(expr.getType())) {
			InfixExpression infix = (InfixExpression) expr;
			Node left = transform(true, infix.getLeft(), acc); 
			Node right = transform(false, infix.getRight(), acc);
			infix.setRight((AstNode) right);
			infix.setLeft((AstNode) left);
			Name name = ASTFactory.name(newName());
			Assignment assign = ASTFactory.assign(name("var " + name.getString()), infix);
			acc.add(ASTFactory.exprStmt(assign));
			return name;
		} else if(expr instanceof InfixExpression){
			InfixExpression infix = (InfixExpression) expr;
			if(infix.getOperator() == Token.AND) {
				
				Node left = transform(lvalue, infix.getLeft(), acc); 
				
				List<Node> rightAcc = new LinkedList<Node>();
				Node right = transform(lvalue, infix.getRight(), rightAcc);
				Name name = ASTFactory.name(newName());
				Block b = new Block();
				for(Node n : rightAcc) {
					b.addStatement((AstNode) n);
				}
				b.addStatement(ASTFactory.exprStmt(ASTFactory.assign(name("var " + name.getString()), (AstNode) right)));
				IfStatement ifStatement = ASTFactory.ifThenElse((AstNode) left, b, ASTFactory.exprStmt(ASTFactory.assign(name("var " + name.getString()), ASTFactory.name("false"))));
				
				acc.add(ifStatement);
				return name;
			} else if(infix.getOperator() == Token.OR) {
				
				Node left = transform(lvalue, infix.getLeft(), acc); 
				
				List<Node> rightAcc = new LinkedList<Node>();
				Node right = transform(lvalue, infix.getRight(), rightAcc);
				Name name = ASTFactory.name(newName());
				Block b = new Block();
				for(Node n : rightAcc) {
					b.addStatement((AstNode) n);
				}
				b.addStatement(ASTFactory.exprStmt(ASTFactory.assign(name("var " + name.getString()), (AstNode) right)));
				IfStatement ifStatement = ASTFactory.ifThenElse((AstNode) left, ASTFactory.exprStmt(ASTFactory.assign(name("var " + name.getString()), (AstNode) left)), b);
	
				acc.add(ifStatement);
				return name;
			}
			else if(lvalue && ContextCheck.isAtomicExpression(infix.getRight())) {
				Node left = transform(false, infix.getLeft(), acc); 
				infix.setLeft((AstNode) left);
				return infix;
			} else {
				Node left = transform(lvalue, infix.getLeft(), acc); 
				Node right = transform(lvalue, infix.getRight(), acc);
				infix.setRight((AstNode) right);
				infix.setLeft((AstNode) left);
				Name name = ASTFactory.name(newName());
				Assignment assign = ASTFactory.assign(name("var " + name.getString()), infix);
				acc.add(ASTFactory.exprStmt(assign));
				return name;
			}
		} else if(expr instanceof PropertyGet) {
			PropertyGet pg = (PropertyGet) expr;
			Node left = transform(lvalue, pg.getLeft(), acc); 
			Node right = transform(lvalue, pg.getRight(), acc);
			pg.setRight((AstNode) right);
			pg.setLeft((AstNode) left);
			Name name = ASTFactory.name(newName());
			Assignment assign = ASTFactory.assign(name("var " + name.getString()), pg);
			acc.add(ASTFactory.exprStmt(assign));
			return name;
		} /*else if(expr instanceof ElementGet) {
			ElementGet eg = (ElementGet) expr;
			Node left = transform(lvalue, eg.getTarget(), acc); 
			Node right = transform(lvalue, eg.getElement(), acc);
			eg.setElement((AstNode) right);
			eg.setTarget((AstNode) left);
			Name name = ASTFactory.name(newName());
			Assignment assign = ASTFactory.assign(name("var " + name.getString()), eg);
			acc.add(ASTFactory.exprStmt(assign));
			return name;
		}*/
		else if(expr instanceof FunctionCall) {
			FunctionCall call = (FunctionCall) expr;
			List<AstNode> args = call.getArguments();
			Node target = call.getTarget();
			boolean transformed = false;
			if(!(target instanceof PropertyGet) || !( ContextCheck.isAtomicExpression(((PropertyGet)target).getLeft()) )) {
				if(target instanceof PropertyGet) {
					PropertyGet pg = (PropertyGet) target;
					AstNode left = pg.getLeft();
					Node newTarget = transform(false, left, acc);
					pg.setLeft((AstNode) newTarget);
					transformed = true;
				} else {
					Node newTarget = transform(false, (AstNode) target, acc);
					call.setTarget((AstNode) newTarget);
					//transformed = true;
				}
			}
			List<AstNode> newArgs = new LinkedList<AstNode>();
			for(AstNode arg : args) {
				newArgs.add((AstNode) transform(false, arg, acc));
			}
			call.setArguments(newArgs);
			if(transformed) {
				return call;
			} else {
				Name name = ASTFactory.name("__retTemp.shift()");
				FunctionCall push = ASTFactory.call("__retTemp.push", expr);
				//Assignment assign = ASTFactory.assign(name, expr);
				acc.add(ASTFactory.exprStmt(push));
				return name;
			}
		}
		else if(expr instanceof ArrayLiteral) {
			ArrayLiteral literal = (ArrayLiteral) expr;
			List<AstNode> args = literal.getElements();
			List<AstNode> newArgs = new LinkedList<AstNode>();
			for(AstNode arg : args) {
				newArgs.add((AstNode) transform(false, arg, acc));
			}
			literal.setElements(newArgs);
			Name name = ASTFactory.name(newName());
			Assignment assign = ASTFactory.assign(name("var " + name.getString()), expr);
			acc.add(ASTFactory.exprStmt(assign));
			return name;
		} else if(expr instanceof ObjectLiteral) {
			ObjectLiteral literal = (ObjectLiteral) expr;
			List<ObjectProperty> args = literal.getElements();
			for(ObjectProperty arg : args) {
				Node newExpr = transform(false, arg.getRight(), acc);
				arg.setRight((AstNode) newExpr);
			}
			Name name = ASTFactory.name(newName());
			Assignment assign = ASTFactory.assign(name("var " + name.getString()), expr);
			acc.add(ASTFactory.exprStmt(assign));
			return name;
		} else if(expr instanceof UnaryExpression) {
			UnaryExpression unary = (UnaryExpression) expr;
			Node newExpr = transform(false, unary.getOperand(), acc);
			unary.setOperand((AstNode) newExpr);
			return unary;
		} else if(expr instanceof FunctionNode) {
			FunctionNode fn = (FunctionNode) expr;
			Block block = (Block) fn.getBody();
			Iterator<Node> children = block.iterator();
			while(children.hasNext()) {
				AstNode child = (AstNode) children.next();
				visit(child);
			}
			return fn;
		}
		else {
			return expr;
		}
	}
	
	@Override
	public boolean visit(AstNode astNode) {
		if(astNode instanceof InfixExpression) {
			if(ContextCheck.isAssignmentOp(astNode.getType())) {
				InfixExpression infix = (InfixExpression) astNode;
				infix.getRight().visit(this);
				return false;
			}
		}
		if((astNode instanceof PropertyGet) || (astNode.getType() == Token.GETPROP)) {
			PropertyGet pg = (PropertyGet) astNode;
			AstNode right = pg.getRight();
			if(pg.getParent().getType() == Token.ASSIGN) {
				/*InfixExpression infix = (InfixExpression) pg.getParent();
				if(infix.getLeft() == pg) {
					if(pg.getRight() instanceof Name) {
						Name name = (Name) pg.getRight();
						FunctionCall call = ASTFactory.call("_set", pg.getLeft(), ASTFactory.name("'" + name.getString() + "'"), infix.getRight());
						tx.replaceNode(infix.getParent(), infix, call);
					}
				}*/
				return false;
			} else if(!(astNode.getParent() instanceof FunctionCall) && (right instanceof Name)) {
				Name name = ASTFactory.name(newName());
				ConditionalExpression ce = new ConditionalExpression();
				PropertyGet newPg = new PropertyGet();
				Assignment assign = ASTFactory.assign(name, newPg);
				newPg.setLeft(pg.getLeft());
				newPg.setRight(pg.getRight());
				//InfixExpression comma = ASTFactory.comma(assign, ASTFactory.get(name,"_id"));
				ce.setFalseExpression(newPg);
				ce.setTrueExpression(new ParenthesizedExpression(ASTFactory.name("null")));
				ce.setTestExpression(ASTFactory.get(newPg, "_id"));
				//FunctionCall call = ASTFactory.call("_get", pg.getLeft(), ASTFactory.name("'" + name.getString() + "'"));
				tx.replaceNode(astNode.getParent(), astNode, ASTFactory.paren(ce));
				
			}
		} else if(astNode instanceof ObjectProperty) {
			ObjectProperty op = (ObjectProperty) astNode;
			op.getRight().visit(this);
		} else if(astNode instanceof ObjectLiteral) {
			ObjectLiteral ol = (ObjectLiteral) astNode;
			for(AstNode node : ol.getElements()) {
				node.visit(this);
			}
		} 
		return true;
	}
	
}